#pragma once

#include <queue>
#include <pthread.h>

template <class T>
class blockqueue
{
public:
    // 构造函数
    blockqueue(int cap = 5)
    {
        _cap = cap;                            // 初始化最大容量
        pthread_mutex_init(&_mutex, nullptr);  // 初始化线程锁
        pthread_cond_init(&_procond, nullptr); // 初始化生产者使用的条件变量
        pthread_cond_init(&_cuscond, nullptr); // 初始化消费者使用的条件变量
    }

    // 析构函数
    ~blockqueue()
    {
        pthread_mutex_destroy(&_mutex);  // 销毁互斥锁
        pthread_cond_destroy(&_procond); // 销毁生产者使用的条件变量
        pthread_cond_destroy(&_cuscond); // 销毁消费者使用的条件变量
    }

    // 生产者向队尾插入数据
    void push(const T &val)
    {
        // 只允许一个线程进入临界区
        pthread_mutex_lock(&_mutex); // 上锁

        // 循环检查队列是否已满，如果已满，就将线程添加到_procond下挂起
        while (is_full())
        {
            pthread_cond_wait(&_procond, &_mutex); // 将生产者线程挂起
            // 该函数调用时会先将_mutex释放，然后将调用的线程挂起
            // 直到消费者取走数据后，才会将生产者唤醒，但可能有多个生产者被唤醒，且全部处于临界区内
            // 线程被唤醒后，该函数会重新竞争锁，没抢到锁的线程只能再次阻塞
            // 有可能其他生产者先抢到锁、并再次将队列填满，因此线程醒来后，需要再次检查队列是否为满
            // 如果队列已满，则只能再次在条件变量下挂起，等待消费者消费数据后再次将生产者唤醒
        }

        // 成功申请到锁，且队列存在空位，进行数据的插入
        _q.push(val);

        pthread_mutex_unlock(&_mutex); // 解锁

        // 生产一个数据后，此时队列中至少有一个数据，将消费者唤醒
        pthread_cond_signal(&_cuscond);
    }

    // 消费者从队列中取数据
    T pop()
    {
        // 只允许一个线程进入临界区
        pthread_mutex_lock(&_mutex); // 上锁

        // 循环检查队列是否为空，如果队列为空，就将线程添加到_cuscond下挂起
        while (is_empty())
        {
            pthread_cond_wait(&_cuscond, &_mutex); // 将消费者线程挂起
            // 该函数调用时会先将_mutex释放，然后将调用的线程挂起
            // 直到生产者输入数据后，才会将消费者唤醒，但可能有多个消费者被唤醒，且全部处于临界区内
            // 线程被唤醒后，该函数会重新竞争锁，没抢到锁的线程只能再次阻塞
            // 有可能其他消费者先抢到锁、先取走数据并再次使队列为空，因此线程醒来后，需要再次检查队列是否为空
            // 如果队列为空，则只能再次在条件变量下挂起，等待生产者输入数据后再次将消费者唤醒
        }

        // 成功申请到锁，且队列不为空，取出队头的数据
        T val = _q.front();
        _q.pop();

        pthread_mutex_unlock(&_mutex); // 解锁

        // 取走一个数据后，此时队列中至少有一个空位，将生产者唤醒
        pthread_cond_signal(&_procond);

        return val; // 返回读取到的数据
    }

    // 判满
    bool is_full()
    {
        return _q.size() >= _cap;
    }

    // 判空
    bool is_empty()
    {
        return _q.size() == 0;
    }

private:
    queue<T> _q;             // 数据队列
    int _cap;                // 队列的最大容量
    pthread_mutex_t _mutex;  // 所有生产者和消费者共用的线程锁
    pthread_cond_t _procond; // 生产者使用的条件变量
    pthread_cond_t _cuscond; // 消费者使用的条件变量
};